﻿#region Includes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

using OpenTK;
using OpenTK.Graphics.OpenGL;
#endregion

namespace GingerGL.Graphics
{
    //------------------------------------------------------------//
    /// <summary>
    /// Texture Class
    /// </summary>
    public class Texture : IDisposable
    {
        //--------------------------------------------------------//
        #region Variables
        static public TextureManager Instance = new TextureManager();
        #endregion
        //--------------------------------------------------------//
        #region Properties
        /// <summary>
        /// Image ID Number
        /// </summary>
        public int TextureId { get; protected set; }
        /// <summary>
        /// Gets texture width
        /// </summary>
        public int Width { get; private set; }
        /// <summary>
        /// Gets texture height
        /// </summary>
        public int Height { get; private set; }
        /// <summary>
        /// Gets/Sets MipMap Buffer Flag
        /// </summary>
        public bool MipMap { get; protected set; }
        /// <summary>
        /// Gets/Set Clamp Flag
        /// </summary>
        public bool Clamp { get; private set; }
        /// <summary>
        /// Gets TRUE if Texture Ready to be drawn
        /// </summary>
        public bool Valid { get; protected set; }
        #endregion
        //--------------------------------------------------------//
        #region Constructors
        /// <summary>
        /// Initializes a new instance from physical system
        /// </summary>
        public Texture(string path) : this(Bitmap.FromFile(path), false) { }
        /// <summary>
        /// Initializes a new instance from physical system
        /// </summary>
        public Texture(string path, bool clamp) : this(Bitmap.FromFile(path), clamp) { }
        /// <summary>
        /// Initializes a new instance from image
        /// </summary>
        public Texture(Image img) : this(img, false) { }
        /// <summary>
        /// Initializes a new instance from image
        /// </summary>
        public Texture(Image img, bool clamp) 
        { 
            this.Clamp = clamp; 
            this.TextureId = -1;
            this.MipMap = true;
            this.Load(img);
        }
        #endregion
        //--------------------------------------------------------//
        #region Methods
        /// <summary>
        /// Clears Texture from OpenGL Memory
        /// </summary>
        public void Invalidate()
        {
            if (TextureId != -1)
                GL.DeleteTexture(TextureId);
            TextureId = -1;
        }
        /// <summary>
        /// Dispose of the texture data
        /// </summary>
        public void Dispose() { Invalidate(); }
        /// <summary>
        /// Load Texture into Memory
        /// </summary>
        public virtual void Load(Image img)
        {
            Bitmap bitmap = (Bitmap)img;
            this.TextureId = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, this.TextureId);
            bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
            BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
            if (this.MipMap) GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
            bitmap.UnlockBits(data);
            bitmap.Dispose();

            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);

            if (this.Clamp)
            {
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
            }
            else
            {
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
            }
            this.Valid = true;
        }
        /// <summary>
        /// Execute Texture Draw or Bind
        /// </summary>
        public void Apply()
        {
            // easy out, do this most of the time
            if (this.TextureId != -1 && Valid)
            {
                GL.Enable(EnableCap.Texture2D);
                GL.BindTexture(TextureTarget.Texture2D, this.TextureId);
                return;
            }
            else GL.Disable(EnableCap.Texture2D);
        }
        /// <summary>
        /// Draw Texture on Screen as Quad
        /// </summary>
        /// <param name="scale">Size of the texture</param>
        public void Draw(float scale)
        {
            GL.Begin(BeginMode.Quads);
            GL.Normal3(0, 0, 1);
            GL.TexCoord2(0, 1);
            GL.Vertex2(0, 0);
            GL.TexCoord2(1, 1);
            GL.Vertex2(Width * scale, 0);
            GL.TexCoord2(1, 0);
            GL.Vertex2(Width * scale, Height * scale);
            GL.TexCoord2(0, 0);
            GL.Vertex2(0, Height * scale);
            GL.End();
        }
        /// <summary>
        /// Draw at width
        /// </summary>
        public void DrawAtWidth(float newWidth)
        {
            Draw(newWidth / Width);
        }
        /// <summary>
        /// Draw at height
        /// </summary>
        public void DrawAtHeight(float newHeight)
        {
            Draw(newHeight / Height);
        }
        #endregion
        //--------------------------------------------------------//
    }
    //------------------------------------------------------------//
}